/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

/*
 * Copyright (c) 2005-2009 Sergey Tkachev http://sergetk.net
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.aphrodite.util;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

import com.aphrodite.security.CheckSum;

/**
 * <p>
 * BitmapFont for mobile applicatiosn.
 * </p>
 * 
 * <p>
 * This class includes a mixed set of methods of Graphics and Font classes. It
 * has character measurement methods from Font, such as stringWidth() and
 * charWidth(). It also has text drawing methods from Graphics, such as
 * drawString() and drawChar(). Text will be drawn by current Graphics color.
 * </p>
 * 
 * <p>
 * You may create your own font using Bitmap Character Editor. This is
 * crossplatform desktop application written on Java using SWT library. It is
 * distributed with <b>aphrodite</b>
 * </p>
 * 
 * <p>
 * Each bitmap font consists of a set of parameters such as height, baseline
 * position etc, of character code map, of array width character widthes and of
 * principal part: one or more images in PNG format store character outlines.
 * </p>
 * 
 * <p>
 * By default all fonts are considered normal. Bold, italic and bold italic
 * styles are generated programmaticaly.
 * </p>
 * <b>It is advisable to call <code>clearFontCache()</code>when
 * <code>getFont(byte)</code> is used</b><br/>
 * 
 */
public final class BitmapFont {
	private final static int DEFAULT_COLOR_CACHE_CAPACITY = 5;

	private static final String PNG_SIGNATURE = "\u0089PNG\r\n\u001A\n";
	public final static byte STYLE_BOLD = 1;
	public final static byte STYLE_ITALIC = 2;

	// private static BitmapFont defaultFont;
	/* font styles */
	public final static byte STYLE_PLAIN = 0;
	public final static byte STYLE_UNDERLINED = 4;
	private static Hashtable usedFontCache = new Hashtable();

	public final static void clearFontCache() {
		System.out.println("[BitmapFont.clearFontCache] Clearing font cache");
		usedFontCache.clear();
	}

	private Image[] baseImages;

	private int baseline;
	private boolean bold;
	private String characterMap;
	private int charWidthIncrement = 0;

	private int[] colorCache;
	private int[] colorUsage;

	private int colorUsageCount;
	private int currentColor;
	private Image[] currentImages;
	private int height;

	private Image[][] imageCache;
	private String imageName;

	private int imagesOffset = 0;
	private boolean italic;
	private int spaceWidth;
	private byte style;

	private int[] widthes, x, y, idx;
	// private byte version;

	private int xIndent;

	private int yIndent;

	private BitmapFont(BitmapFont font, byte style) {
		this.imageName = font.imageName;
		this.currentImages = this.baseImages = font.baseImages;

		this.height = font.height;
		this.baseline = font.baseline;
		this.xIndent = font.xIndent;
		this.yIndent = font.yIndent;
		this.spaceWidth = font.spaceWidth;

		this.style = style;
		this.italic = (style & BitmapFont.STYLE_ITALIC) != 0;
		this.bold = (style & BitmapFont.STYLE_BOLD) != 0;
		this.currentColor = 0;
		this.imagesOffset = font.imagesOffset;

		this.characterMap = font.characterMap;
		this.widthes = font.widthes;
		this.x = font.x;
		this.y = font.y;
		this.idx = font.idx;

		this.colorUsageCount = font.colorUsageCount;
		this.colorUsage = font.colorUsage;
		this.colorCache = font.colorCache;
		this.imageCache = font.imageCache;

		this.charWidthIncrement = bold ? 1 : 0;
	}

	public BitmapFont(InputStream input, String name) {
		init(input, name, DEFAULT_COLOR_CACHE_CAPACITY);
	}

	// /**
	// * increase the height by the number of points
	// *
	// * @param points
	// */
	// public void increaseHeight(int points) {
	// System.out.println("");
	// }

	/**
	 * Creates a new font from the resource.
	 * 
	 * @param fontName
	 *            the resource name
	 */
	public BitmapFont(String fontName) {
		this(fontName, DEFAULT_COLOR_CACHE_CAPACITY);
	}

	/**
	 * Creates a new font from the resource. The capacity of the color cache
	 * defines maximum size of the color cache.
	 * 
	 * @param fontName
	 *            the resource name
	 * @param colorCacheCapacity
	 *            the maximum color cache size
	 */
	public BitmapFont(String fontName, int colorCacheCapacity) {
		this.style = BitmapFont.STYLE_PLAIN;
		this.currentColor = 0;

		init(new Object().getClass().getResourceAsStream(fontName), fontName, colorCacheCapacity);
		// usedFontCache.put(Integer.toString(this.getStyle()), this);
	}

	/**
	 * Gets the index of the character.
	 * 
	 * @param c
	 *            the character
	 * @return the index of the character
	 */
	protected final int charIndex(final char c) {
		try {
			return characterMap.indexOf(c);
		} catch (IndexOutOfBoundsException e) {
			return -1;
		}
	}

	/**
	 * Gets the width of the characters, starting at the specified offset and
	 * for the specified number of characters (length).
	 * 
	 * @param ch
	 *            the array of characters
	 * @param offset
	 *            zero-based index of a first character
	 * @param length
	 *            the number of characters to measure
	 * @return the width in pixels
	 */
	public final int charsWidth(final char[] ch, final int offset, final int length) {
		int w = 0;
		for (int i = offset; i < offset + length; i++) {
			w += charWidth(ch[i]);
		}
		return w;
	}

	/**
	 * Gets the width of the specified character in this font.
	 * 
	 * @param c
	 *            the character to be measured
	 * @return the width of the character
	 */
	public final int charWidth(final char c) {
		if (c == ' ') {
			return spaceWidth + xIndent + charWidthIncrement;
		}
		int index = charIndex(c);
		if (index < 0) {
			return spaceWidth + xIndent + charWidthIncrement;
		} else {
			return widthes[index] + xIndent + charWidthIncrement;
		}
	}

	/**
	 * Replaces black color in the palette chunk to the specified color.
	 * 
	 * @param buffer
	 *            the byte array
	 * @param offset
	 *            the offset of the start of the data in the array
	 * @param color
	 *            the color to replace
	 */
	private final void colorizePalette(final byte[] buffer, final int offset, final int color) {
		int dataLength = getInt(buffer, offset);
		int dataOffset = offset + 8;

		int r = (color & 0x00FF0000) >>> 16;
		int g = (color & 0x0000FF00) >>> 8;
		int b = (color & 0x000000FF);

		for (int i = 0; i < dataLength / 3; i++) {
			int pR = buffer[dataOffset + 0] & 0xFF;
			int pG = buffer[dataOffset + 1] & 0xFF;
			int pB = buffer[dataOffset + 2] & 0xFF;

			int brightness = (pR + pG + pB) / 3;

			buffer[dataOffset++] = (byte) (r + (brightness * (255 - r)) / 255); // red
			buffer[dataOffset++] = (byte) (g + (brightness * (255 - g)) / 255); // green
			buffer[dataOffset++] = (byte) (b + (brightness * (255 - b)) / 255); // blue
		}

		int crc = CheckSum.doCRC32(buffer, offset + 4, dataLength + 4);
		setInt(buffer, offset + 8 + dataLength, crc);
	}

	/**
	 * Compare byte sequence with string
	 * 
	 * @param buffer
	 *            the byte array
	 * @param offset
	 *            the offset of the start of the data in the array
	 * @param str
	 *            the string to compare with bytes in the buffer
	 * @return true if the buffer contains the string
	 */
	private final boolean compareBytes(final byte[] buffer, final int offset, final String str) {
		for (int i = 0; i < str.length(); i++) {
			if (((byte) (str.charAt(i))) != buffer[i + offset]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Draws the specified character.
	 * 
	 * @param g
	 *            the graphics context
	 * @param c
	 *            the character to be drawn
	 * @param x
	 *            the x coordinate of the anchor point
	 * @param y
	 *            the y coordinate of the anchor point
	 * @return the x coordinate for the next character
	 */
	public final int drawChar(final Graphics g, final char c, final int x, final int y) {
		setColor(g.getColor());
		int nextX = drawOneChar(g, c, x, y);
		if ((style & BitmapFont.STYLE_UNDERLINED) != 0) {
			int yU = y + this.baseline + 2;
			g.drawLine(x, yU, nextX - 1, yU);
		}
		return nextX;
	}

	/**
	 * Draws the specified characters.
	 * 
	 * @param g
	 *            the graphics context
	 * @param data
	 *            the array of characters to be drawn
	 * @param offset
	 *            the start offset in the data
	 * @param length
	 *            the number of characters to be drawn
	 * @param x
	 *            the x coordinate of the anchor point
	 * @param y
	 *            the y coordinate of the anchor point
	 * @param anchors
	 *            the anchor point for positioning the text
	 * @return the x coordinate for the next character
	 */
	public final int drawChars(final Graphics g, final char[] data, final int offset, final int length, final int x,
			final int y, final int anchors) {
		int xx = getX(charsWidth(data, offset, length), x, anchors);
		int yy = getY(y, anchors);
		setColor(g.getColor());
		for (int i = offset; i < offset + length; i++) {
			xx = drawOneChar(g, data[i], xx, yy);
		}
		if ((style & BitmapFont.STYLE_UNDERLINED) != 0) {
			int yU = y + this.baseline + 2;
			g.drawLine(x, yU, xx - 1, yU);
		}
		return xx;
	}

	// public final int getYIndent() {
	// return yIndent;
	// }

	/**
	 * Draws one character. It called from drawChar(), drawString() and
	 * drawSubstrung().
	 * 
	 * @param g
	 *            the graphics context
	 * @param c
	 *            the character to be drawn
	 * @param x
	 *            the x coordinate of the anchor point
	 * @param y
	 *            the y coordinate of the anchor point
	 * @return the x coordinate for the next character
	 */
	private final int drawOneChar(final Graphics g, final char c, final int x, int y) {
		// skip if it is a space
		if (c == ' ') {
			return x + this.spaceWidth + xIndent + charWidthIncrement;
		}
		int charIndex = charIndex(c);
		// draw the unknown character as a rectangle
		if (charIndex < 0) {
			int squareWidth = this.spaceWidth + xIndent + charWidthIncrement;
			g.drawRect(x, y, squareWidth - 1, height - 1);
			return x + squareWidth;
		}

		int charX = this.x[charIndex];
		int charY = this.y[charIndex];
		int cw = widthes[charIndex];
		int imageIndex = idx[charIndex];

		y += yIndent / 2;

		Image image = this.currentImages[imageIndex];

		int clipX = g.getClipX();
		int clipY = g.getClipY();
		int clipWidth = g.getClipWidth();
		int clipHeight = g.getClipHeight();

		int ix = x - charX;
		int iy = y - charY;

		if (!italic && !bold) {
			g.clipRect(x, y, cw, this.height);
			g.drawImage(image, ix, iy, Graphics.LEFT | Graphics.TOP);
		} else if (italic & bold) {
			int halfHeight = height / 2;
			g.clipRect(x + 1, y, cw, this.height);
			g.drawImage(image, ix + 1, iy, Graphics.LEFT | Graphics.TOP);
			g.setClip(clipX, clipY, clipWidth, clipHeight);
			g.clipRect(x + 2, y, cw, halfHeight);
			g.drawImage(image, ix + 2, iy, Graphics.LEFT | Graphics.TOP);
			g.setClip(clipX, clipY, clipWidth, clipHeight);
			g.clipRect(x, y + halfHeight, cw, height - halfHeight);
			g.drawImage(image, ix, iy, Graphics.LEFT | Graphics.TOP);
		} else if (italic) {
			int halfHeight = height / 2;
			g.clipRect(x + 1, y, cw, halfHeight);
			g.drawImage(image, ix + 1, iy, Graphics.LEFT | Graphics.TOP);
			g.setClip(clipX, clipY, clipWidth, clipHeight);
			g.clipRect(x, y + halfHeight, cw, height - halfHeight);
			g.drawImage(image, ix, iy, Graphics.LEFT | Graphics.TOP);
		} else { // just a bold
			g.clipRect(x, y, cw, this.height);
			g.drawImage(image, ix, iy, Graphics.LEFT | Graphics.TOP);
			g.setClip(clipX, clipY, clipWidth, clipHeight);
			g.clipRect(x + 1, y, cw, this.height);
			g.drawImage(image, ix + 1, iy, Graphics.LEFT | Graphics.TOP);
		}
		// restore clipping
		g.setClip(clipX, clipY, clipWidth, clipHeight);
		return x + cw + xIndent + charWidthIncrement;
	}

	/**
	 * Draws the specified string.
	 * 
	 * @param g
	 *            the graphics context
	 * @param text
	 *            the text to be drawn
	 * @param x
	 *            the x coordinate of the anchor point
	 * @param y
	 *            the y coordinate of the anchor point
	 * @param anchors
	 *            the anchor point for positioning of the text
	 * @return the x coordinate for the next string
	 */
	public final int drawString(final Graphics g, final String text, final int x, final int y, final int anchors) {
		return drawSubstring(g, text, 0, text.length(), x, y, anchors);
	}

	/**
	 * Draws the specified substring.
	 * 
	 * @param g
	 *            the graphics context
	 * @param text
	 *            the text to be drawn
	 * @param offset
	 *            the index of a first character
	 * @param length
	 *            the number of characters
	 * @param x
	 *            the x coordinate of the anchor point
	 * @param y
	 *            the y coordinate of the anchor point
	 * @param anchors
	 *            the anchor point for positioning the text
	 * @return the x coordinate for the next string
	 */
	public final int drawSubstring(final Graphics g, final String text, final int offset, final int length,
			final int x, final int y, final int anchors) {
		int xx = getX(substringWidth(text, offset, length), x, anchors);
		int yy = getY(y, anchors);
		setColor(g.getColor());
		for (int i = offset; i < offset + length; i++) {
			xx = drawOneChar(g, text.charAt(i), xx, yy);
		}
		if ((style & BitmapFont.STYLE_UNDERLINED) != 0) {
			int yU = y + this.baseline + 2;
			g.drawLine(x, yU, xx - 1, yU);
		}
		return xx;
	}

	/**
	 * Gets the distance from the top of the text to the text baseline.
	 * 
	 * @return the baseline position in pixels
	 */
	public final int getBaselinePosition() {
		return baseline;
	}

	/**
	 * Finds the specified chunk.
	 * 
	 * @param buffer
	 *            the byte array
	 * @param offset
	 *            the offset of the start of the data in the array
	 * @param chunk
	 *            the name of chunk (i.e. PLTE)
	 * @return the offset of chunk (-1 if chunk isn't present)
	 */
	private final int getChunk(final byte[] buffer, int offset, final String chunk) {
		try {
			for (;;) {
				int dataLenght = getInt(buffer, offset);
				if (compareBytes(buffer, offset + 4, chunk)) {
					return offset;
				} else {
					offset += 4 + 4 + dataLenght + 4;
				}
			}
		} catch (Exception e) {
		}
		;
		return -1;
	}

	/**
	 * Loads images from the resource and replace palette chunks.
	 * 
	 * @param name
	 *            the name of the resource containing the image data in the PNG
	 *            format
	 * @param color
	 *            the color
	 * @return the created image
	 */
	private final Image[] getColorizedImages(final String name, final int skip, final int color) {
		InputStream inputStream = getClass().getResourceAsStream(name);
		DataInputStream dataStream = new DataInputStream(inputStream);

		Image[] images = null;

		try {
			dataStream.skip(skip);
			int imagesCount = dataStream.readByte();
			images = new Image[imagesCount];

			for (int i = 0; i < imagesCount; i++) {
				int imageLength = dataStream.readShort();
				byte[] buffer = new byte[imageLength];
				dataStream.read(buffer, 0, imageLength);

				if (!compareBytes(buffer, 0, PNG_SIGNATURE)) {
					return null;
				}

				int paletteOffset = getChunk(buffer, 8, "PLTE");
				if (paletteOffset >= 0) {
					colorizePalette(buffer, paletteOffset, color);
					images[i] = Image.createImage(buffer, 0, imageLength);
				}
			}
		} catch (Exception e) {
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
			}
			;
		}

		return images;
	}

	/**
	 * 
	 * Creates a new font if it does not exits in the font cache
	 * 
	 * @param style
	 *            the style of the font
	 * @return the font
	 */
	public final BitmapFont getFont(final byte style) {
		String uniqueName = this.getUniqueName(this.imageName, style);
		if (!usedFontCache.containsKey(uniqueName)) {
			System.out.println("[BitmapFont] Creating a new Font " + uniqueName + " in font cache");
			usedFontCache.put(uniqueName, new BitmapFont(this, style));
		}

		return (BitmapFont) usedFontCache.get(uniqueName);
		// return new BitmapFont(this, style);
	}

	/**
	 * Gets the standard height of a line of a text in this font.
	 * 
	 * @return the height in pixels
	 */
	public final int getHeight() {
		return height + yIndent;
	}

	/**
	 * height without the yindent of the font
	 * 
	 * @return font
	 */
	public final int getHeightOnly() {
		return height;
	}

	/* ================= Character measurement functions =============== */

	/**
	 * Get the integer value from the four bytes. The most signified byte go
	 * first.
	 * 
	 * @param buffer
	 *            the byte array
	 * @param offset
	 *            the offset of the start of the data in the array
	 * @return the integer value
	 */
	private final int getInt(final byte[] buffer, int offset) {
		int result = buffer[offset++] << 24;
		result |= (buffer[offset++] << 16) & 0x00FF0000;
		result |= (buffer[offset++] << 8) & 0x0000FF00;
		result |= buffer[offset] & 0x000000FF;

		return result;
	}

	/**
	 * Gets the style of the font.
	 * 
	 * @return style
	 */
	public final byte getStyle() {
		return this.style;
	}

	private String getUniqueName(String name, byte style) {
		return new java.lang.StringBuffer('#').append(name).append(style).append('#').toString();
	}

	private final int getX(final int w, final int x, final int anchors) {
		if ((anchors & Graphics.RIGHT) != 0) {
			return x - w;
		} else if ((anchors & Graphics.HCENTER) != 0) {
			return x - w / 2;
		}
		return x;
	}

	/* ================= Working with the PNG =============== */

	private final int getY(final int y, final int anchors) {
		if ((anchors & Graphics.BOTTOM) != 0) {
			return y - height;
		} else if ((anchors & Graphics.VCENTER) != 0) {
			return y - height / 2;
		} else if ((anchors & Graphics.BASELINE) != 0) {
			return y - this.getBaselinePosition();
		}
		return y;
	}

	private final void init(final InputStream input, final String fontName, final int colorCacheCapacity) {

		try {
			if (input == null) {
				throw new IOException("[BitmapFont] Could not find font");
			}
			DataInputStream data = new DataInputStream(input);
			imagesOffset = data.available() - 1;
			// System.out.println("imagesOffset : " + imagesOffset);
			this.imageName = fontName;

			// this.version = data.readByte();
			data.readByte();
			this.height = data.readByte();
			// System.out.println("Height value " + this.height);
			this.baseline = data.readByte();
			// System.out.println("baseline value " + this.baseline);
			this.xIndent = data.readByte();
			// System.out.println("xIndent value " + this.xIndent);
			this.yIndent = data.readByte();
			// System.out.println("yIndent value " + this.yIndent);
			this.spaceWidth = data.readByte();
			// System.out.println("Space width: " + spaceWidth);
			characterMap = data.readUTF();
			// System.out.println("character map: " + characterMap);
			int count = characterMap.length();
			// System.out.println("number of characters " + count);
			// read characters widthes
			this.widthes = new int[count];
			this.x = new int[count];
			this.y = new int[count];
			this.idx = new int[count];
			// System.out.println("reading the width of each character ");
			StringBuffer buf = new StringBuffer("new int[]{");
			for (int i = 0; i < count; i++) {
				widthes[i] = data.readByte();
				buf.append(widthes[i]).append(',');
			}
			buf.append("};");
			// System.out.println(buf.toString());
			// read font images
			int imagesCount = data.readByte();
			// System.out.println("number of images " + imagesCount);
			baseImages = new Image[imagesCount];

			for (int i = 0; i < imagesCount; i++) {
				int imageLength = data.readShort();
				byte[] buffer = new byte[imageLength];

				data.read(buffer, 0, imageLength);
				baseImages[i] = Image.createImage(buffer, 0, imageLength);
				imagesOffset -= imageLength + 2;
			}

			currentImages = baseImages;

			// calculate characters coordinates
			int curX = 0, curY = 0, curIdx = 0;
			int curImageWidth = currentImages[0].getWidth();
			int curImageHeight = currentImages[0].getHeight();

			for (int i = 0; i < count; i++) {
				if (widthes[i] < 0) {
					// negative width points to another character
					int sourceIndex = -widthes[i];
					widthes[i] = widthes[sourceIndex];
					x[i] = x[sourceIndex];
					y[i] = y[sourceIndex];
					idx[i] = idx[sourceIndex];
				} else {
					if (curX + widthes[i] > curImageWidth) {
						curX = 0;
						curY += height;
						if (curY > curImageHeight) {
							curY = 0;
							curIdx++;
							curImageWidth = currentImages[curIdx].getWidth();
							curImageHeight = currentImages[curIdx].getHeight();
						}
					}
					x[i] = curX;
					y[i] = curY;
					idx[i] = curIdx;
					curX += widthes[i];
				}
			}
			colorCache = new int[colorCacheCapacity];
			colorUsage = new int[colorCacheCapacity];
			imageCache = new Image[colorCacheCapacity][];

			// if (defaultFont == null)
			// defaultFont = this;
			data.close();
			usedFontCache.put(getUniqueName(this.imageName, this.style), this);
		} catch (IOException e) {
		}
	}

	public final void setColor(int color) {
		color &= 0x00FFFFFF;
		if (this.currentColor == color) {
			return;
		}

		this.currentColor = color;
		if (color == 0x00000000) { // new color is black
			this.currentImages = this.baseImages;
		} else {
			int cacheItemIndex = 0;
			int minUsage = Integer.MAX_VALUE;
			for (int i = 0; i < colorCache.length; i++) {
				if (colorCache[i] == color) {
					// the color is already in the cache
					currentImages = imageCache[i];
					colorUsage[i] = colorUsageCount++;
					return;
				} else if (colorCache[i] == 0) {
					// cache item is empty
					cacheItemIndex = i;
					break;
				} else if (colorUsage[i] < minUsage) {
					minUsage = colorUsage[i];
					cacheItemIndex = i;
				}
			}
			final Image[] images = getColorizedImages(this.imageName, this.imagesOffset, color);
			colorCache[cacheItemIndex] = color;
			imageCache[cacheItemIndex] = images;
			colorUsage[cacheItemIndex] = colorUsageCount++;
			this.currentImages = images;
		}
	}

	/**
	 * Set four bytes to the specified value. The most signified byte go first.
	 * 
	 * @param buffer
	 *            the byte array
	 * @param offset
	 *            the offset of the start of the data in the array
	 * @param value
	 *            the value to set
	 */
	private final void setInt(final byte[] buffer, int offset, final int value) {
		buffer[offset++] = (byte) ((value & 0xFF000000) >>> 24);
		buffer[offset++] = (byte) ((value & 0x00FF0000) >>> 16);
		buffer[offset++] = (byte) ((value & 0x0000FF00) >>> 8);
		buffer[offset] = (byte) ((value & 0x000000FF));
	}

	/**
	 * set the width of the space character
	 * 
	 * @param width
	 *            default is 4 px
	 */
	public void setSpaceWidth(byte width) {
		this.spaceWidth = width;
	}

	/**
	 * Gets the width of the string.
	 * 
	 * @param str
	 *            the String to be measured
	 * @return the width in pixels
	 */
	public final int stringWidth(final String str) {
		return substringWidth(str, 0, str.length());
	}

	/**
	 * Gets the width of the substring.
	 * 
	 * @param str
	 *            the string to be measured
	 * @param offset
	 *            zero-based index of a first character in the substring
	 * @param length
	 *            the number of characters to measure
	 * @return the length of the substring
	 */
	public final int substringWidth(final String str, final int offset, final int length) {
		int w = 0;
		for (int i = offset; i < offset + length; i++) {
			w += charWidth(str.charAt(i));
		}
		return w;
	}

	// public void cleanUp() {
	// clearFontCache();
	// }
}
